/*
 * UAE - The Un*x Amiga Emulator
 *
 * Not a parser, but parallel and serial emulation for Win32
 *
 * Copyright 1997 Mathias Ortmann
 * Copyright 1998-1999 Brian King - added MIDI output support
 */

#include "sysconfig.h"
#include "options.h"
#include "commpipe.h"
#include "zfile.h"
#include "gensound.h"
#include "parser.h"
#include "midi.h"
#include "dxwrap.h"
#include "picasso96_win.h"
#include "ioport.h"
#include "parallel.h"
#include "serial.h"
#include "uaeipc.h"
#include "drawing.h"
#include "win32.h"
#include <Ghostscript/iapi.h>
#include <Ghostscript/ierrors.h>
#include <ws2tcpip.h>

#undef SERIAL_ENET

#define MIN_PRTBYTES 10

static char prtbuf[PRTBUFSIZE];
static int prtbufbytes, wantwrite;
static HANDLE hPrt = INVALID_HANDLE_VALUE;
static DWORD dwJob;
static int prtopen;
void DoSomeWeirdPrintingStuff(char val);
static int uartbreak;
static int parflush;

static volatile int prt_running;
static volatile int prt_started;
static smp_comm_pipe prt_requests;

// int postscript_print_debugging = 0;
static struct zfile* prtdump;

static int psmode = 0;
static HMODULE gsdll;
static void* gsinstance;
static int gs_exitcode;

typedef int (CALLBACK * GSAPI_REVISION)(gsapi_revision_t* pr, int len);
static GSAPI_REVISION ptr_gsapi_revision;
typedef int (CALLBACK * GSAPI_NEW_INSTANCE)(void** pinstance, void* caller_handle);
static GSAPI_NEW_INSTANCE ptr_gsapi_new_instance;
typedef void (CALLBACK * GSAPI_DELETE_INSTANCE)(void* instance);
static GSAPI_DELETE_INSTANCE ptr_gsapi_delete_instance;
typedef int (CALLBACK * GSAPI_SET_STDIO)(void* instance,
    int (GSDLLCALLPTR stdin_fn)(void* caller_handle, char* buf, int len),
    int (GSDLLCALLPTR stdout_fn)(void* caller_handle, const char* str, int len),
    int (GSDLLCALLPTR stderr_fn)(void* caller_handle, const char* str, int len));
static GSAPI_SET_STDIO ptr_gsapi_set_stdio;
typedef int (CALLBACK * GSAPI_INIT_WITH_ARGS)(void* instance, int argc, char** argv);
static GSAPI_INIT_WITH_ARGS ptr_gsapi_init_with_args;

typedef int (CALLBACK * GSAPI_EXIT)(void* instance);
static GSAPI_EXIT ptr_gsapi_exit;

typedef int (CALLBACK * GSAPI_RUN_STRING_BEGIN)(void* instance, int user_errors, int* pexit_code);
static GSAPI_RUN_STRING_BEGIN ptr_gsapi_run_string_begin;
typedef int (CALLBACK * GSAPI_RUN_STRING_CONTINUE)(void* instance, const char* str, uint length, int user_errors, int* pexit_code);
static GSAPI_RUN_STRING_CONTINUE ptr_gsapi_run_string_continue;
typedef int (CALLBACK * GSAPI_RUN_STRING_END)(void* instance, int user_errors, int* pexit_code);
static GSAPI_RUN_STRING_END ptr_gsapi_run_string_end;

static byte** psbuffer;
static int psbuffers;

static LONG WINAPI ExceptionFilter(struct _EXCEPTION_POINTERS* pExceptionPointers, DWORD ec)
{
    return EXCEPTION_EXECUTE_HANDLER;
}

static void freepsbuffers()
{
    int i;
    for (i = 0; i < psbuffers; i++)
        free(psbuffer[i]);
    free(psbuffer);
    psbuffer = nullptr;
    psbuffers = 0;
}

static int openprinter_ps()
{
    TCHAR* gsargv[] = {
        L"-dNOPAUSE", L"-dBATCH", L"-dNOPAGEPROMPT", L"-dNOPROMPT", L"-dQUIET", L"-dNoCancel",
        L"-sDEVICE=mswinpr2", nullptr
    };
    int gsargc, gsargc2, i;
    TCHAR* tmpparms[100];
    TCHAR tmp[MAX_DPATH];
    char* gsparms[100];

    if (ptr_gsapi_new_instance(&gsinstance, nullptr) < 0)
        return 0;
    cmdlineparser(g_curr_conf.ghostscript_parameters, tmpparms, 100 - 10);

    gsargc2 = 0;
    gsparms[gsargc2++] = Unicode::ua(L"WinUAE");
    for (gsargc = 0; gsargv[gsargc]; gsargc++)
    {
        gsparms[gsargc2++] = Unicode::ua(gsargv[gsargc]);
    }
    for (i = 0; tmpparms[i]; i++)
        gsparms[gsargc2++] = Unicode::ua(tmpparms[i]);
    if (g_curr_conf.prtname != "")
    {
        _stprintf(tmp, L"-sOutputFile=%%printer%%%s", g_curr_conf.prtname);
        gsparms[gsargc2++] = Unicode::ua(tmp);
    }
    if (Logging::PRINT_PS_DEBUG)
    {
        for (i = 0; i < gsargc2; i++)
        {
            TCHAR* parm = Unicode::au(gsparms[i]);
            Logger::Write(L"GSPARM%d: '%s'\n", i, parm);
            free(parm);
        }
    }
    __try {
        int rc = ptr_gsapi_init_with_args(gsinstance, gsargc2, gsparms);
        for (i = 0; i < gsargc2; i++)
        {
            free(gsparms[i]);
        }
        if (rc != 0)
        {
            Logger::Write(L"GS failed, returncode %d\n", rc);
            return 0;
        }
        ptr_gsapi_run_string_begin(gsinstance, 0, &gs_exitcode);
    } __except(ExceptionFilter(GetExceptionInformation(), GetExceptionCode()))
    {
        Logger::Write(L"GS crashed\n");
        return 0;
    }
    psmode = 1;
    return 1;
}

static void* prt_thread(void* p)
{
    byte** buffers = (byte**)p;
    int err, cnt, ok;

    ok = 1;
    prt_running++;
    prt_started = 1;
    SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL);
    if (load_ghostscript())
    {
        if (openprinter_ps())
        {
            Logger::Write(L"PostScript printing emulation started..\n");
            cnt = 0;
            while (buffers[cnt])
            {
                byte* p = buffers[cnt];
                err = ptr_gsapi_run_string_continue(gsinstance, (char*)p + 2, (p[0] << 8) | p[1], 0, &gs_exitcode);
                if (err != e_NeedInput && err <= e_Fatal)
                {
                    ptr_gsapi_exit(gsinstance);
                    Logger::Write(L"PostScript parsing failed.\n");
                    ok = 0;
                    break;
                }
                cnt++;
            }
            cnt = 0;
            while (buffers[cnt])
            {
                free(buffers[cnt]);
                cnt++;
            }
            free(buffers);
            if (ok)
            {
                Logger::Write(L"PostScript printing emulation finished..\n");
                ptr_gsapi_run_string_end(gsinstance, 0, &gs_exitcode);
            }
        }
        else
        {
            Logger::Write(L"gsdll32.dll failed to initialize\n");
        }
    }
    else
    {
        Logger::Write(L"gsdll32.dll failed to load\n");
    }
    unload_ghostscript();
    prt_running--;
    return 0;
}

static int doflushprinter()
{
    if (prtopen == 0 && prtbufbytes < MIN_PRTBYTES)
    {
        if (prtbufbytes > 0)
            Logger::Write(L"PRINTER: %d bytes received, less than %d bytes, not printing.\n", prtbufbytes, MIN_PRTBYTES);
        prtbufbytes = 0;
        return 0;
    }
    return 1;
}

static void openprinter();
static void flushprtbuf()
{
    DWORD written = 0;

    if (!prtbufbytes)
        return;

    if (Logging::PRINT_PS_DEBUG && prtdump)
        zfile_fwrite(prtbuf, prtbufbytes, 1, prtdump);

    if (g_curr_conf.parallel_postscript_emulation)
    {
        if (psmode)
        {
            byte* p;
            psbuffer = xrealloc(byte *, psbuffer, (psbuffers + 2));
            p = xmalloc(byte, prtbufbytes + 2);
            p[0] = prtbufbytes >> 8;
            p[1] = prtbufbytes;
            memcpy(p + 2, prtbuf, prtbufbytes);
            psbuffer[psbuffers++] = p;
            psbuffer[psbuffers] = nullptr;
        }
        prtbufbytes = 0;
        return;
    }
    else if (prtbufbytes > 0)
    {
        int pbyt = prtbufbytes;

        if (g_curr_conf.parallel_matrix_emulation >= PARALLEL_MATRIX_EPSON)
        {
            int i;
            if (!prtopen)
            {
                if (!doflushprinter())
                    return;
                if (epson_init(g_curr_conf.prtname, g_curr_conf.parallel_matrix_emulation))
                    prtopen = 1;
            }
            for (i = 0; i < prtbufbytes; i++)
                epson_printchar(prtbuf[i]);
        }
        else
        {
            if (hPrt == INVALID_HANDLE_VALUE)
            {
                if (!doflushprinter())
                    return;
                openprinter();
            }
            if (hPrt != INVALID_HANDLE_VALUE)
            {
                if (WritePrinter(hPrt, prtbuf, pbyt, &written))
                {
                    if (written != pbyt)
                        Logger::Write(L"PRINTER: Only wrote %d of %d bytes!\n", written, pbyt);
                }
                else
                {
                    Logger::Write(L"PRINTER: Couldn't write data!\n");
                }
            }
        }
    }
    prtbufbytes = 0;
}

void finishjob()
{
    flushprtbuf();
}

/*static*/ void DoSomeWeirdPrintingStuff(char val)
{
    static char prev[5];

    memmove(prev, prev + 1, 3);
    prev[3] = val;
    prev[4] = 0;
    if (g_curr_conf.parallel_postscript_detection)
    {
        if (psmode && val == 4)
        {
            flushprtbuf();
            *prtbuf = val;
            prtbufbytes = 1;
            flushprtbuf();
            Logger::Write(L"PostScript end detected..\n");

            if (Logging::PRINT_PS_DEBUG)
            {
                zfile_fclose(prtdump);
                prtdump = nullptr;
            }

            if (g_curr_conf.parallel_postscript_emulation)
            {
                prt_started = 0;
                if (uae_start_thread(L"postscript", prt_thread, psbuffer, nullptr))
                {
                    while (!prt_started)
                        Sleep(5);
                    psbuffers = 0;
                    psbuffer = nullptr;
                }
            }
            else
            {
                closeprinter();
            }
            freepsbuffers();
            return;
        }
        else if (!psmode && !_stricmp(prev, "%!PS"))
        {
            if (Logging::PRINT_PS_DEBUG)
                prtdump = zfile_fopen(L"psdump.dat", L"wb", 0);

            psmode = 1;
            psbuffer = xmalloc(byte *, 1);
            psbuffer[0] = 0;
            psbuffers = 0;
            strcpy(prtbuf, "%!PS");
            prtbufbytes = strlen(prtbuf);
            flushprtbuf();
            Logger::Write(L"PostScript start detected..\n");
            return;
        }
    }
    if (prtbufbytes < PRTBUFSIZE)
    {
        prtbuf[prtbufbytes++] = val;
    }
    else
    {
        flushprtbuf();
        *prtbuf = val;
        prtbufbytes = 1;
    }
}

int isprinter()
{
    if (g_curr_conf.prtname == "")
        return 0;
    if (!_tcsncmp(g_curr_conf.prtname, L"LPT", 3))
    {
        paraport_open(g_curr_conf.prtname);
        return -1;
    }
    return 1;
}

int isprinteropen()
{
    if (prtopen || prtbufbytes > 0)
        return 1;
    return 0;
}

int load_ghostscript()
{
    struct gsapi_revision_s r;
    TCHAR path[MAX_DPATH];
    TCHAR* s;

    if (gsdll)
        return 1;
    _tcscpy(path, L"gsdll32.dll");
    gsdll = WIN32_LoadLibrary(path);
    if (!gsdll)
    {
        if (GetEnvironmentVariable(L"GS_DLL", path, sizeof(path) / sizeof(TCHAR)))
            gsdll = LoadLibrary(path);
    }
    if (!gsdll)
    {
        HKEY key;
        DWORD ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"SOFTWARE\\AFPL Ghostscript", 0, KEY_READ, &key);
        if (ret != ERROR_SUCCESS)
            ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"SOFTWARE\\GPL Ghostscript", 0, KEY_READ, &key);
        if (ret == ERROR_SUCCESS)
        {
            int idx = 0, cnt = 20;
            TCHAR tmp1[MAX_DPATH];
            while (cnt-- > 0)
            {
                DWORD size1 = sizeof(tmp1) / sizeof(TCHAR);
                FILETIME ft;
                if (RegEnumKeyEx(key, idx, tmp1, &size1, nullptr, nullptr, nullptr, &ft) == ERROR_SUCCESS)
                {
                    HKEY key2;
                    if (RegOpenKeyEx(key, tmp1, 0, KEY_READ, &key2) == ERROR_SUCCESS)
                    {
                        DWORD type = REG_SZ;
                        DWORD size = sizeof(path) / sizeof(TCHAR);
                        if (RegQueryValueEx(key2, L"GS_DLL", 0, &type, (LPBYTE)path, &size) == ERROR_SUCCESS)
                        {
                            gsdll = LoadLibrary(path);
                        }
                        RegCloseKey(key2);
                        if (gsdll)
                            break;
                    }
                }
                idx++;
            }
            RegCloseKey(key);
        }
    }
    if (!gsdll)
        return 0;
    ptr_gsapi_revision = (GSAPI_REVISION)GetProcAddress(gsdll, "gsapi_revision");
    if (!ptr_gsapi_revision)
    {
        unload_ghostscript();
        Logger::Write(L"incompatible %s! (1)\n", path);
        return -1;
    }
    if (ptr_gsapi_revision(&r, sizeof(r)))
    {
        unload_ghostscript();
        Logger::Write(L"incompatible %s! (2)\n", path);
        return -2;
    }
    ptr_gsapi_new_instance = (GSAPI_NEW_INSTANCE)GetProcAddress(gsdll, "gsapi_new_instance");
    ptr_gsapi_delete_instance = (GSAPI_DELETE_INSTANCE)GetProcAddress(gsdll, "gsapi_delete_instance");
    ptr_gsapi_set_stdio = (GSAPI_SET_STDIO)GetProcAddress(gsdll, "gsapi_set_stdio");
    ptr_gsapi_exit = (GSAPI_EXIT)GetProcAddress(gsdll, "gsapi_exit");
    ptr_gsapi_run_string_begin = (GSAPI_RUN_STRING_BEGIN)GetProcAddress(gsdll, "gsapi_run_string_begin");
    ptr_gsapi_run_string_continue = (GSAPI_RUN_STRING_CONTINUE)GetProcAddress(gsdll, "gsapi_run_string_continue");
    ptr_gsapi_run_string_end = (GSAPI_RUN_STRING_END)GetProcAddress(gsdll, "gsapi_run_string_end");
    ptr_gsapi_init_with_args = (GSAPI_INIT_WITH_ARGS)GetProcAddress(gsdll, "gsapi_init_with_args");
    if (!ptr_gsapi_new_instance || !ptr_gsapi_delete_instance || !ptr_gsapi_exit ||
        !ptr_gsapi_run_string_begin || !ptr_gsapi_run_string_continue || !ptr_gsapi_run_string_end ||
        !ptr_gsapi_init_with_args)
    {
        unload_ghostscript();
        Logger::Write(L"incompatible %s! (3)\n", path);
        return -3;
    }
    s = Unicode::au(r.product);
    Logger::Write(L"%s: %s rev %d initialized\n", path, s, r.revision);
    free(s);
    return 1;
}

void unload_ghostscript()
{
    if (gsinstance)
    {
        ptr_gsapi_exit(gsinstance);
        ptr_gsapi_delete_instance(gsinstance);
    }
    gsinstance = nullptr;
    if (gsdll)
        FreeLibrary(gsdll);
    gsdll = nullptr;
    psmode = 0;
}

static void openprinter()
{
    DOC_INFO_1 DocInfo;
    static int first;

    closeprinter();
    if (g_curr_conf.prtname == "")
        return;

    if (g_curr_conf.parallel_postscript_emulation)
    {
        prtopen = 1;
        return;
    }
    else if (g_curr_conf.parallel_matrix_emulation >= PARALLEL_MATRIX_EPSON)
    {
        epson_init(g_curr_conf.prtname, g_curr_conf.parallel_matrix_emulation);
    }
    else if (hPrt == INVALID_HANDLE_VALUE)
    {
        flushprtbuf();

        if (OpenPrinter(const_cast<LPTSTR>((LPCTSTR)g_curr_conf.prtname), &hPrt, nullptr))
        {
            //  Fill in the structure with info about this "document."
            DocInfo.pDocName = L"WinUAE Document";
            DocInfo.pOutputFile = nullptr;
            DocInfo.pDatatype = (g_curr_conf.parallel_matrix_emulation || g_curr_conf.parallel_postscript_detection) ? L"TEXT" : L"RAW";
            //  Inform the spooler the document is beginning.
            if ((dwJob = StartDocPrinter(hPrt, 1, (LPBYTE)&DocInfo)) == 0)
            {
                ClosePrinter(hPrt);
                hPrt = INVALID_HANDLE_VALUE;
            }
            else if (StartPagePrinter(hPrt))
            {
                prtopen = 1;
            }
        }
        else
        {
            hPrt = INVALID_HANDLE_VALUE; // Stupid bug in Win32, where OpenPrinter fails, but hPrt ends up being zero
        }
    }
    if (hPrt != INVALID_HANDLE_VALUE)
    {
        Logger::Write(L"PRINTER: Opening printer \"%s\" with handle 0x%x.\n", g_curr_conf.prtname, hPrt);
    }
    else if (g_curr_conf.prtname != "")
    {
        Logger::Write(L"PRINTER: ERROR - Couldn't open printer \"%s\" for output.\n", g_curr_conf.prtname);
    }
}

void flushprinter()
{
    if (!doflushprinter())
        return;
    flushprtbuf();
    closeprinter();
}

void closeprinter()
{
    #ifdef PRINT_DUMP
    zfile_fclose(prtdump);
    #endif
    parflush = 0;
    psmode = 0;
    if (hPrt != INVALID_HANDLE_VALUE)
    {
        EndPagePrinter(hPrt);
        EndDocPrinter(hPrt);
        ClosePrinter(hPrt);
        hPrt = INVALID_HANDLE_VALUE;
        Logger::Write(L"PRINTER: Closing printer.\n");
    }
    if (g_curr_conf.parallel_postscript_emulation)
        prtopen = 1;
    else
        prtopen = 0;
    if (prt_running)
    {
        Logger::Write(L"waiting for printing to finish...\n");
        while (prt_running)
            Sleep(10);
    }
    freepsbuffers();
    epson_close();
    prtbufbytes = 0;
}

void doprinter(byte val)
{
    parflush = 0;
    DoSomeWeirdPrintingStuff(val);
}

struct uaeserialdatawin32
{
    HANDLE hCom;
    HANDLE evtr, evtw, evtt, evtwce;
    OVERLAPPED olr, olw, olwce;
    int writeactive;
    void* readdata, * writedata;
    volatile int threadactive;
    uae_sem_t change_sem, sync_sem;
    void* user;
};

int uaeser_getdatalenght()
{
    return sizeof(struct uaeserialdatawin32);
}

static void uaeser_initdata(void* vsd, void* user)
{
    struct uaeserialdatawin32* sd = (struct uaeserialdatawin32*)vsd;
    memset(sd, 0, sizeof(struct uaeserialdatawin32));
    sd->hCom = INVALID_HANDLE_VALUE;
    sd->evtr = sd->evtw = sd->evtt = sd->evtwce = 0;
    sd->user = user;
}

int uaeser_query(void* vsd, ushort* status, uint* pending)
{
    struct uaeserialdatawin32* sd = (struct uaeserialdatawin32*)vsd;
    DWORD err, modem;
    COMSTAT ComStat;
    ushort s = 0;

    if (!ClearCommError(sd->hCom, &err, &ComStat))
        return 0;
    *pending = ComStat.cbInQue;
    if (status)
    {
        s |= (err & CE_BREAK) ? (1 << 10) : 0;
        s |= (err & CE_RXOVER) ? (1 << 8) : 0;
        if (GetCommModemStatus(sd->hCom, &modem))
        {
            s |= (modem & MS_CTS_ON) ? 0 : (1 << 4);
            s |= (modem & MS_DSR_ON) ? 0 : (1 << 7);
            s |= (modem & MS_RING_ON) ? (1 << 2) : 0;
        }
        *status = s;
    }
    return 1;
}

int uaeser_break(void* vsd, int brklen)
{
    struct uaeserialdatawin32* sd = (struct uaeserialdatawin32*)vsd;
    if (!SetCommBreak(sd->hCom))
        return 0;
    Sleep(brklen / 1000);
    ClearCommBreak(sd->hCom);
    return 1;
}

int uaeser_setparams(void* vsd, int baud, int rbuffer, int bits, int sbits, int rtscts, int parity, uint xonxoff)
{
    struct uaeserialdatawin32* sd = (struct uaeserialdatawin32*)vsd;
    DCB dcb;

    memset(&dcb, 0, sizeof(dcb));
    dcb.DCBlength = sizeof(DCB);
    if (!GetCommState(sd->hCom, &dcb))
        return 5;

    dcb.fBinary = TRUE;
    dcb.BaudRate = baud;
    dcb.ByteSize = bits;
    dcb.Parity = parity == 0 ? NOPARITY : (parity == 1 ? ODDPARITY : EVENPARITY);
    dcb.fParity = FALSE;
    dcb.StopBits = sbits == 1 ? ONESTOPBIT : TWOSTOPBITS;

    dcb.fDsrSensitivity = FALSE;
    dcb.fOutxDsrFlow = FALSE;
    dcb.fDtrControl = DTR_CONTROL_DISABLE;

    if (rtscts)
    {
        dcb.fOutxCtsFlow = TRUE;
        dcb.fRtsControl = RTS_CONTROL_HANDSHAKE;
    }
    else
    {
        dcb.fRtsControl = RTS_CONTROL_DISABLE;
        dcb.fOutxCtsFlow = FALSE;
    }

    dcb.fTXContinueOnXoff = FALSE;
    if (xonxoff & 1)
    {
        dcb.fOutX = TRUE;
        dcb.fInX = TRUE;
        dcb.XonChar = (xonxoff >> 8) & 0xff;
        dcb.XoffChar = (xonxoff >> 16) & 0xff;
    }
    else
    {
        dcb.fOutX = FALSE;
        dcb.fInX = FALSE;
    }

    dcb.fErrorChar = FALSE;
    dcb.fNull = FALSE;
    dcb.fAbortOnError = FALSE;

    // dcb.XoffLim = 512;
    // dcb.XonLim = 2048;

    if (!SetCommState(sd->hCom, &dcb))
    {
        Logger::Write(L"uaeserial: SetCommState() failed %d\n", GetLastError());
        return 5;
    }
    SetupComm(sd->hCom, rbuffer, rbuffer);
    return 0;
}

static void startwce(struct uaeserialdatawin32* sd, DWORD* evtmask)
{
    SetEvent(sd->evtwce);
    WaitCommEvent(sd->hCom, evtmask, &sd->olwce);
}

static void* uaeser_trap_thread(void* arg)
{
    struct uaeserialdatawin32* sd = (struct uaeserialdatawin32*)arg;
    HANDLE handles[4];
    int cnt;
    DWORD evtmask, actual;

    uae_set_thread_priority(nullptr, 1);
    sd->threadactive = 1;
    uae_sem_post(&sd->sync_sem);
    startwce(sd, &evtmask);
    while (sd->threadactive == 1)
    {
        int sigmask = 0;
        uae_sem_wait(&sd->change_sem);
        if (WaitForSingleObject(sd->evtwce, 0) == WAIT_OBJECT_0)
        {
            if (evtmask & EV_RXCHAR)
                sigmask |= 1;
            if ((evtmask & EV_TXEMPTY) && !sd->writeactive)
                sigmask |= 2;
            startwce(sd, &evtmask);
        }
        cnt = 0;
        handles[cnt++] = sd->evtt;
        handles[cnt++] = sd->evtwce;
        if (sd->writeactive)
        {
            if (GetOverlappedResult(sd->hCom, &sd->olw, &actual, FALSE))
            {
                sd->writeactive = 0;
                sigmask |= 2;
            }
            else
            {
                handles[cnt++] = sd->evtw;
            }
        }
        if (!sd->writeactive)
            sigmask |= 2;
        uaeser_signal(sd->user, sigmask | 1);
        uae_sem_post(&sd->change_sem);
        WaitForMultipleObjects(cnt, handles, FALSE, INFINITE);
    }
    sd->threadactive = 0;
    uae_sem_post(&sd->sync_sem);
    return 0;
}

void uaeser_trigger(void* vsd)
{
    struct uaeserialdatawin32* sd = (struct uaeserialdatawin32*)vsd;
    SetEvent(sd->evtt);
}

int uaeser_write(void* vsd, byte* data, uint len)
{
    struct uaeserialdatawin32* sd = (struct uaeserialdatawin32*)vsd;
    int ret = 1;
    if (!WriteFile(sd->hCom, data, len, nullptr, &sd->olw))
    {
        sd->writeactive = 1;
        if (GetLastError() != ERROR_IO_PENDING)
        {
            ret = 0;
            sd->writeactive = 0;
        }
    }
    SetEvent(sd->evtt);
    return ret;
}

int uaeser_read(void* vsd, byte* data, uint len)
{
    struct uaeserialdatawin32* sd = (struct uaeserialdatawin32*)vsd;
    int ret = 1;
    DWORD err;
    COMSTAT ComStat;

    if (!ClearCommError(sd->hCom, &err, &ComStat))
        return 0;
    if (len > ComStat.cbInQue)
        return 0;
    if (!ReadFile(sd->hCom, data, len, nullptr, &sd->olr))
    {
        if (GetLastError() == ERROR_IO_PENDING)
            WaitForSingleObject(sd->evtr, INFINITE);
        else
            ret = 0;
    }
    SetEvent(sd->evtt);
    return ret;
}

void uaeser_clearbuffers(void* vsd)
{
    struct uaeserialdatawin32* sd = (struct uaeserialdatawin32*)vsd;
    PurgeComm(sd->hCom, PURGE_TXCLEAR | PURGE_RXCLEAR);
}

int uaeser_open(void* vsd, void* user, int unit)
{
    struct uaeserialdatawin32* sd = (struct uaeserialdatawin32*)vsd;
    TCHAR buf[256];
    COMMTIMEOUTS CommTimeOuts;

    sd->user = user;
    _stprintf(buf, L"\\\\.\\COM%d", unit);
    sd->evtr = CreateEvent(nullptr, TRUE, FALSE, nullptr);
    sd->evtw = CreateEvent(nullptr, TRUE, FALSE, nullptr);
    sd->evtt = CreateEvent(nullptr, FALSE, FALSE, nullptr);
    sd->evtwce = CreateEvent(nullptr, TRUE, FALSE, nullptr);
    if (!sd->evtt || !sd->evtw || !sd->evtt || !sd->evtwce)
        goto end;
    sd->olr.hEvent = sd->evtr;
    sd->olw.hEvent = sd->evtw;
    sd->olwce.hEvent = sd->evtwce;
    sd->hCom = CreateFile(buf, GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, nullptr);
    if (sd->hCom == INVALID_HANDLE_VALUE)
    {
        _stprintf(buf, L"\\.\\\\COM%d", unit);
        sd->hCom = CreateFile(buf, GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING,
            FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, nullptr);
        if (sd->hCom == INVALID_HANDLE_VALUE)
        {
            Logger::Write(L"UAESER: '%s' failed to open, err=%d\n", buf, GetLastError());
            goto end;
        }
    }
    uae_sem_init(&sd->sync_sem, 0, 0);
    uae_sem_init(&sd->change_sem, 0, 1);
    uae_start_thread(L"uaeserial_win32", uaeser_trap_thread, sd, nullptr);
    uae_sem_wait(&sd->sync_sem);

    CommTimeOuts.ReadIntervalTimeout = 0;
    CommTimeOuts.ReadTotalTimeoutMultiplier = 0;
    CommTimeOuts.ReadTotalTimeoutConstant = 0;
    CommTimeOuts.WriteTotalTimeoutMultiplier = 0;
    CommTimeOuts.WriteTotalTimeoutConstant = 0;
    SetCommTimeouts(sd->hCom, &CommTimeOuts);
    SetCommMask(sd->hCom, EV_RXCHAR | EV_TXEMPTY | EV_BREAK);

    return 1;

end:
    uaeser_close(sd);
    return 0;
}

void uaeser_close(void* vsd)
{
    struct uaeserialdatawin32* sd = (struct uaeserialdatawin32*)vsd;
    if (sd->threadactive)
    {
        sd->threadactive = -1;
        SetEvent(sd->evtt);
        while (sd->threadactive)
            Sleep(10);
        CloseHandle(sd->evtt);
    }
    if (sd->hCom != INVALID_HANDLE_VALUE)
        CloseHandle(sd->hCom);
    if (sd->evtr)
        CloseHandle(sd->evtr);
    if (sd->evtw)
        CloseHandle(sd->evtw);
    if (sd->evtwce)
        CloseHandle(sd->evtwce);
    uaeser_initdata(sd, sd->user);
}

static HANDLE hCom = INVALID_HANDLE_VALUE;
static DCB dcb;
static HANDLE writeevent, readevent;
#define SERIAL_WRITE_BUFFER 100
#define SERIAL_READ_BUFFER 100
static byte outputbuffer[SERIAL_WRITE_BUFFER];
static byte outputbufferout[SERIAL_WRITE_BUFFER];
static byte inputbuffer[SERIAL_READ_BUFFER];
static int datainoutput;
static int dataininput, dataininputcnt;
static OVERLAPPED writeol, readol;
static int writepending;

static WSADATA wsadata;
static SOCKET serialsocket = INVALID_SOCKET;
static SOCKET serialconn = INVALID_SOCKET;
static PADDRINFOW socketinfo;
static char socketaddr[sizeof SOCKADDR_INET];
static BOOL tcpserial;

static bool tcp_is_connected()
{
    socklen_t sa_len = sizeof SOCKADDR_INET;
    if (serialsocket == INVALID_SOCKET)
        return false;
    if (serialconn == INVALID_SOCKET)
    {
        struct timeval tv;
        fd_set fd;
        tv.tv_sec = 0;
        tv.tv_usec = 0;
        fd.fd_array[0] = serialsocket;
        fd.fd_count = 1;
        if (select(1, &fd, NULL, NULL, &tv))
        {
            serialconn = accept(serialsocket, (struct sockaddr*)socketaddr, &sa_len);
            if (serialconn != INVALID_SOCKET)
                Logger::Write(L"SERIRAL_TCP: connection accepted\n");
        }
    }
    return serialconn != INVALID_SOCKET;
}

static void tcp_disconnect()
{
    if (serialconn == INVALID_SOCKET)
        return;
    closesocket(serialconn);
    serialconn = INVALID_SOCKET;
    Logger::Write(L"SERIAL_TCP: disconnect\n");
}

static void closetcp()
{
    if (serialconn != INVALID_SOCKET)
        closesocket(serialconn);
    serialconn = INVALID_SOCKET;
    if (serialsocket != INVALID_SOCKET)
        closesocket(serialsocket);
    serialsocket = INVALID_SOCKET;
    if (socketinfo)
        FreeAddrInfoW(socketinfo);
    socketinfo = NULL;
    WSACleanup();
}

static int opentcp(const TCHAR* sername)
{
    int err;
    TCHAR* port, * name;
    const TCHAR* p;
    bool waitmode = false;
    const int one = 1;
    const struct linger linger_1s = { 1, 1 };

    if (WSAStartup(MAKEWORD(2, 2), &wsadata))
    {
        DWORD lasterror = WSAGetLastError();
        Logger::Write(L"SERIAL_TCP: can't open '%s', error %d\n", sername, lasterror);
        return 0;
    }
    name = _tcsdup(sername);
    port = NULL;
    p = _tcschr(sername, ':');
    if (p)
    {
        name[p - sername] = 0;
        port = _tcsdup(p + 1);
        const TCHAR* p2 = _tcschr(port, '/');
        if (p2)
        {
            port[p2 - port] = 0;
            if (!_tcsicmp(p2 + 1, L"wait"))
                waitmode = true;
        }
    }
    if (port && port[0] == 0)
    {
        free(port);
        port = NULL;
    }
    if (!port)
        port = _tcsdup(L"1234");

    err = GetAddrInfoW(name, port, NULL, &socketinfo);
    if (err < 0)
    {
        Logger::Write(L"SERIAL_TCP: GetAddrInfoW() failed, %s:%s: %d\n", name, port, WSAGetLastError());
        goto end;
    }
    serialsocket = socket(socketinfo->ai_family, socketinfo->ai_socktype, socketinfo->ai_protocol);
    if (serialsocket == INVALID_SOCKET)
    {
        Logger::Write(L"SERIAL_TCP: socket() failed, %s:%s: %d\n", name, port, WSAGetLastError());
        goto end;
    }
    err = bind(serialsocket, socketinfo->ai_addr, socketinfo->ai_addrlen);
    if (err < 0)
    {
        Logger::Write(L"SERIAL_TCP: bind() failed, %s:%s: %d\n", name, port, WSAGetLastError());
        goto end;
    }
    err = listen(serialsocket, 1);
    if (err < 0)
    {
        Logger::Write(L"SERIAL_TCP: listen() failed, %s:%s: %d\n", name, port, WSAGetLastError());
        goto end;
    }
    err = setsockopt(serialsocket, SOL_SOCKET, SO_LINGER, (char*)&linger_1s, sizeof linger_1s);
    if (err < 0)
    {
        Logger::Write(L"SERIAL_TCP: setsockopt(SO_LINGER) failed, %s:%s: %d\n", name, port, WSAGetLastError());
        goto end;
    }
    err = setsockopt(serialsocket, SOL_SOCKET, SO_REUSEADDR, (char*)&one, sizeof one);
    if (err < 0)
    {
        Logger::Write(L"SERIAL_TCP: setsockopt(SO_REUSEADDR) failed, %s:%s: %d\n", name, port, WSAGetLastError());
        goto end;
    }

    while (tcp_is_connected() == false)
    {
        Sleep(1000);
        Logger::Write(L"SERIAL_TCP: waiting for connect...\n");
    }

    free(port);
    free(name);
    tcpserial = TRUE;
    return 1;
end:
    free(port);
    free(name);
    closetcp();
    return 0;
}
int openser(const TCHAR* sername)
{
    COMMTIMEOUTS CommTimeOuts;

    if (!_tcsnicmp(sername, L"TCP://", 6))
    {
        return opentcp(sername + 6);
    }
    if (!_tcsnicmp(sername, L"TCP:", 4))
    {
        return opentcp(sername + 4);
    }
    if (!(readevent = CreateEvent(nullptr, TRUE, FALSE, nullptr)))
    {
        Logger::Write(L"SERIAL: Failed to create r event!\n");
        return 0;
    }
    readol.hEvent = readevent;

    if (!(writeevent = CreateEvent(nullptr, TRUE, FALSE, nullptr)))
    {
        Logger::Write(L"SERIAL: Failed to create w event!\n");
        return 0;
    }
    SetEvent(writeevent);
    writeol.hEvent = writeevent;

    uartbreak = 0;

    hCom = CreateFile(sername, GENERIC_READ | GENERIC_WRITE,
        0,
        nullptr,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
        nullptr);
    if (hCom == INVALID_HANDLE_VALUE)
    {
        Logger::Write(L"SERIAL: failed to open '%s' err=%d\n", sername, GetLastError());
        closeser();
        return 0;
    }

    SetCommMask(hCom, EV_RXFLAG);
    SetupComm(hCom, 65536, 128);
    PurgeComm(hCom, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR);
    CommTimeOuts.ReadIntervalTimeout = 0xFFFFFFFF;
    CommTimeOuts.ReadTotalTimeoutMultiplier = 0;
    CommTimeOuts.ReadTotalTimeoutConstant = 0;
    CommTimeOuts.WriteTotalTimeoutMultiplier = 0;
    CommTimeOuts.WriteTotalTimeoutConstant = 0;
    SetCommTimeouts(hCom, &CommTimeOuts);

    dcb.DCBlength = sizeof(DCB);
    GetCommState(hCom, &dcb);

    dcb.fBinary = TRUE;
    dcb.BaudRate = 9600;
    dcb.ByteSize = 8;
    dcb.Parity = NOPARITY;
    dcb.fParity = FALSE;
    dcb.StopBits = g_curr_conf.serial_stopbits;

    dcb.fDsrSensitivity = FALSE;
    dcb.fOutxDsrFlow = FALSE;
    dcb.fDtrControl = DTR_CONTROL_DISABLE;

    if (g_curr_conf.serial_hwctsrts)
    {
        dcb.fOutxCtsFlow = TRUE;
        dcb.fRtsControl = RTS_CONTROL_HANDSHAKE;
    }
    else
    {
        dcb.fRtsControl = RTS_CONTROL_DISABLE;
        dcb.fOutxCtsFlow = FALSE;
    }

    dcb.fTXContinueOnXoff = FALSE;
    dcb.fOutX = FALSE;
    dcb.fInX = FALSE;

    dcb.fErrorChar = FALSE;
    dcb.fNull = FALSE;
    dcb.fAbortOnError = FALSE;

    // dcb.XoffLim = 512;
    // dcb.XonLim = 2048;

    if (SetCommState(hCom, &dcb))
    {
        Logger::Write(L"SERIAL: Using %s CTS/RTS=%d\n", sername, g_curr_conf.serial_hwctsrts);
        return 1;
    }

    Logger::Write(L"SERIAL: serial driver didn't accept new parameters\n");
    closeser();
    return 0;
}

void closeser()
{
    if (tcpserial)
    {
        closetcp();
        tcpserial = FALSE;
    }
    if (hCom != INVALID_HANDLE_VALUE)
    {
        CloseHandle(hCom);
        hCom = INVALID_HANDLE_VALUE;
    }
    if (midi_ready)
    {
        extern ushort serper;
        Midi_Close();
        // need for camd Midi Stuff(it close midi and reopen it but serial.c think the baudrate
        // is the same and do not open midi), so setting serper to different value helps
        serper = 0x30;
    }
    if (writeevent)
        CloseHandle(writeevent);
    writeevent = 0;
    if (readevent)
        CloseHandle(readevent);
    readevent = 0;
    uartbreak = 0;
}

static void outser()
{
    DWORD actual;
    if (WaitForSingleObject(writeevent, 0) == WAIT_OBJECT_0 && datainoutput > 0)
    {
        memcpy(outputbufferout, outputbuffer, datainoutput);
        WriteFile(hCom, outputbufferout, datainoutput, &actual, &writeol);
        datainoutput = 0;
    }
}

void writeser(int c)
{
    if (tcpserial)
    {
        if (tcp_is_connected())
        {
            char buf[1];
            buf[0] = (char)c;
            if (send(serialconn, buf, 1, 0) != 1)
            {
                tcp_disconnect();
            }
        }
    }
    else if (midi_ready)
    {
        BYTE outchar = (BYTE)c;
        Midi_Parse(midi_output, &outchar);
    }
    else
    {
        if (!g_curr_conf.use_serial)
            return;
        if (datainoutput + 1 < sizeof(outputbuffer))
        {
            outputbuffer[datainoutput++] = c;
        }
        else
        {
            Logger::Write(L"serial output buffer overflow, data will be lost\n");
            datainoutput = 0;
        }
        outser();
    }
}

int checkserwrite()
{
    if (hCom == INVALID_HANDLE_VALUE || !g_curr_conf.use_serial)
        return 1;
    if (midi_ready)
    {
        return 1;
    }
    else
    {
        outser();
        if (datainoutput >= sizeof(outputbuffer) - 1)
            return 0;
    }
    return 1;
}

int readseravail()
{
    COMSTAT ComStat;
    DWORD dwErrorFlags;

    if (tcpserial)
    {
        if (tcp_is_connected())
        {
            struct timeval tv;
            fd_set fd;
            tv.tv_sec = 0;
            tv.tv_usec = 0;
            fd.fd_array[0] = serialconn;
            fd.fd_count = 1;
            int err = select(1, &fd, NULL, NULL, &tv);
            if (err == SOCKET_ERROR)
            {
                tcp_disconnect();
                return 0;
            }
            if (err > 0)
                return 1;
        }
        return 0;
    }
    else if (midi_ready)
    {
        if (ismidibyte())
            return 1;
    }
    else
    {
        if (!g_curr_conf.use_serial)
            return 0;
        if (dataininput > dataininputcnt)
            return 1;
        if (hCom != INVALID_HANDLE_VALUE)
        {
            ClearCommError(hCom, &dwErrorFlags, &ComStat);
            if (ComStat.cbInQue > 0)
                return 1;
        }
    }
    return 0;
}

int readser(int* buffer)
{
    COMSTAT ComStat;
    DWORD dwErrorFlags;
    DWORD actual;

    if (tcpserial)
    {
        if (tcp_is_connected())
        {
            char buf[1];
            buf[0] = 0;
            int err = recv(serialconn, buf, 1, 0);
            if (err == 1)
            {
                *buffer = buf[0];
                return 1;
            }
            else
            {
                tcp_disconnect();
            }
        }
        return 0;
    }
    else if (midi_ready)
    {
        *buffer = getmidibyte();
        if (*buffer < 0)
            return 0;
        return 1;
    }
    else
    {
        if (!g_curr_conf.use_serial)
            return 0;
        if (dataininput > dataininputcnt)
        {
            *buffer = inputbuffer[dataininputcnt++];
            return 1;
        }
        dataininput = 0;
        dataininputcnt = 0;
        if (hCom != INVALID_HANDLE_VALUE)
        {
            /* only try to read number of bytes in queue */
            ClearCommError(hCom, &dwErrorFlags, &ComStat);
            if (ComStat.cbInQue)
            {
                int len = ComStat.cbInQue;
                if (len > sizeof(inputbuffer))
                    len = sizeof(inputbuffer);
                if (!ReadFile(hCom, inputbuffer, len, &actual, &readol))
                {
                    if (GetLastError() == ERROR_IO_PENDING)
                        WaitForSingleObject(&readol, INFINITE);
                    else
                        return 0;
                }
                dataininput = actual;
                dataininputcnt = 0;
                if (actual == 0)
                    return 0;
                return readser(buffer);
            }
        }
    }
    return 0;
}

void serialuartbreak(int v)
{
    if (hCom == INVALID_HANDLE_VALUE || !g_curr_conf.use_serial)
        return;

    if (v)
        EscapeCommFunction(hCom, SETBREAK);
    else
        EscapeCommFunction(hCom, CLRBREAK);
}

void getserstat(int* pstatus)
{
    DWORD stat;
    int status = 0;

    *pstatus = 0;
    if (hCom == INVALID_HANDLE_VALUE || !g_curr_conf.use_serial)
        return;

    GetCommModemStatus(hCom, &stat);
    if (stat & MS_CTS_ON)
        status |= TIOCM_CTS;
    if (stat & MS_RLSD_ON)
        status |= TIOCM_CAR;
    if (stat & MS_DSR_ON)
        status |= TIOCM_DSR;
    if (stat & MS_RING_ON)
        status |= TIOCM_RI;
    *pstatus = status;
}

void setserstat(int mask, int onoff)
{
    if (!g_curr_conf.use_serial || hCom == INVALID_HANDLE_VALUE)
        return;

    if (mask & TIOCM_DTR)
        EscapeCommFunction(hCom, onoff ? SETDTR : CLRDTR);
    if (!g_curr_conf.serial_hwctsrts)
    {
        if (mask & TIOCM_RTS)
            EscapeCommFunction(hCom, onoff ? SETRTS : CLRRTS);
    }
}

int setbaud(long baud)
{
    if (baud == 31400 && g_curr_conf.win32_midioutdev >= -1)
    {
        /* MIDI baud-rate */
        if (!midi_ready)
        {
            if (Midi_Open())
                Logger::Write(L"Midi enabled\n");
        }
        return 1;
    }
    else
    {
        if (midi_ready)
        {
            Midi_Close();
        }
        if (!g_curr_conf.use_serial)
            return 1;
        if (hCom != INVALID_HANDLE_VALUE)
        {
            if (GetCommState(hCom, &dcb))
            {
                dcb.BaudRate = baud;
                if (!SetCommState(hCom, &dcb))
                {
                    Logger::Write(L"SERIAL: Error setting baud rate %d!\n", baud);
                    return 0;
                }
            }
            else
            {
                Logger::Write(L"SERIAL: setbaud internal error!\n");
            }
        }
    }
    return 1;
}

// void initparallel()
// {
//     if (uae_boot_rom)
//     {
//         uae_ptr a = here();  //this install the ahisound
//         org(rtarea_base + 0xFFC0);
//         calltrap(deftrapres(ahi_demux, 0, L"ahi_winuae"));
//         dw(RTS);
//         org(a);
//         init_ahi_v2();
//     }
// }

int flashscreen;

void doflashscreen()
{
    flashscreen = 10;
    init_colors();
    picasso_refresh();
    reset_drawing();
    flush_screen(0, 0);
}

void hsyncstuff()
{
    // only generate Interrupts when
    // writebuffer is complete flushed
    // check state of lwin rwin
    static int keycheck = 0;

    // #ifdef AHI
    // { //begin ahi_sound
    //     static int count;
    //     if (ahi_on)
    //     {
    //         count++;
    //         //15625/count freebuffer check
    //         if (count > ahi_pollrate)
    //         {
    //             ahi_updatesound(1);
    //             count = 0;
    //         }
    //     }
    // } //end ahi_sound
    // #endif
    #ifdef PARALLEL_PORT
    keycheck++;
    if (keycheck >= 1000)
    {
        if (prtopen)
            flushprtbuf();
        {
            if (flashscreen > 0)
            {
                flashscreen--;
                if (flashscreen == 0)
                {
                    init_colors();
                    reset_drawing();
                    picasso_refresh();
                    flush_screen(0, 0);
                }
            }
        }
        keycheck = 0;
    }
    if (g_curr_conf.parallel_autoflush_time && !g_curr_conf.parallel_postscript_detection)
    {
        parflush++;
        if (parflush / ((g_curr_conf.ntscmode ? MAXVPOS_NTSC : MAXVPOS_PAL) * MAXHPOS_PAL / maxhpos) >= g_curr_conf.parallel_autoflush_time * 50)
        {
            flushprinter();
            parflush = 0;
        }
    }
    #endif
}

const /*static*/ GUID GUID_DEVINTERFACE_PARALLEL = { 0x97F76EF0, 0xF883, 0x11D0,
                                                     { 0xAF, 0x1F, 0x00, 0x00, 0xF8, 0x00, 0x84, 0x5C }
};

static int enumports_2(struct serparportinfo* pi, int cnt, bool parport)
{
    //  Create a device information set that will be the container for
    //  the device interfaces.
    HDEVINFO hDevInfo = INVALID_HANDLE_VALUE;
    SP_DEVICE_INTERFACE_DETAIL_DATA* pDetData = nullptr;
    BOOL bOk = TRUE;
    SP_DEVICE_INTERFACE_DATA ifcData;
    DWORD dwDetDataSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA) + 256 * sizeof(TCHAR);
    DWORD ii;

    hDevInfo = SetupDiGetClassDevs(parport ? &GUID_DEVINTERFACE_PARALLEL : &GUID_DEVINTERFACE_COMPORT, nullptr, nullptr, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
    if (hDevInfo == INVALID_HANDLE_VALUE)
        return 0;
    //  Enumerate the serial ports
    pDetData = (SP_DEVICE_INTERFACE_DETAIL_DATA*)xmalloc(byte, dwDetDataSize);
    //  This is required, according to the documentation. Yes,
    //  it's weird.
    ifcData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
    pDetData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
    for (ii = 0; bOk; ii++)
    {
        bOk = SetupDiEnumDeviceInterfaces(hDevInfo, nullptr, parport ? &GUID_DEVINTERFACE_PARALLEL : &GUID_DEVINTERFACE_COMPORT, ii, &ifcData);
        if (bOk)
        {
            //  Got a device. Get the details.
            SP_DEVINFO_DATA devdata = { sizeof(SP_DEVINFO_DATA) };
            bOk = SetupDiGetDeviceInterfaceDetail(hDevInfo,
                &ifcData, pDetData, dwDetDataSize, nullptr, &devdata);
            if (bOk)
            {
                //  Got a path to the device. Try to get some more info.
                TCHAR fname[256];
                TCHAR desc[256];
                BOOL bSuccess = SetupDiGetDeviceRegistryProperty(
                    hDevInfo, &devdata, SPDRP_FRIENDLYNAME, nullptr,
                    (PBYTE)fname, sizeof(fname), nullptr);
                bSuccess = bSuccess && SetupDiGetDeviceRegistryProperty(
                    hDevInfo, &devdata, SPDRP_DEVICEDESC, nullptr,
                    (PBYTE)desc, sizeof(desc), nullptr);
                if (bSuccess && cnt < MAX_SERPAR_PORTS)
                {
                    TCHAR* p;
                    pi[cnt].dev = pDetData->DevicePath;
                    pi[cnt].name = fname;
                    p = _tcsstr(fname, parport ? L"(LPT" : L"(COM");
                    if (p && (p[5] == ')' || p[6] == ')'))
                    {
                        TCHAR str[100];

                        if (isdigit(p[5]))
                            _stprintf(str, parport ? L"LPT%c%c" : L"COM%c%c", p[4], p[5]);
                        else
                            _stprintf(str, parport ? L"LPT%c" : L"COM%c", p[4]);

                        pi[cnt].cfgname = str;
                    }
                    else
                    {
                        pi[cnt].cfgname = pDetData->DevicePath;
                    }

                    if (Logging::PARSER_DEBUG)
                        Logger::Write(L"%s: '%s' = '%s' = '%s'\n", parport ? L"PARPORT" : L"SERPORT", pi[cnt].name, pi[cnt].cfgname, pi[cnt].dev);

                    cnt++;
                }
            }
            else
            {
                Logger::Write(L"SetupDiGetDeviceInterfaceDetail failed, err=%d", GetLastError());
                goto end;
            }
        }
        else
        {
            DWORD err = GetLastError();
            if (err != ERROR_NO_MORE_ITEMS)
            {
                Logger::Write(L"SetupDiEnumDeviceInterfaces failed, err=%d", err);
                goto end;
            }
        }
    }
end:
    free(pDetData);
    if (hDevInfo != INVALID_HANDLE_VALUE)
        SetupDiDestroyDeviceInfoList(hDevInfo);
    return cnt;
}

struct serparportinfo parports[MAX_SERPAR_PORTS];

int enumserialports()
{
    int cnt, i, j;
    TCHAR name[256];
    // DWORD size = sizeof(COMMCONFIG);
    TCHAR devname[1000];

    if (Logging::PARSER_DEBUG)
        Logger::Write(L"Serial port enumeration..\n");

    cnt = 0;

    #ifdef SERIAL_ENET
    comports[cnt].dev = _tcsdup(L"ENET:H");
    comports[cnt].cfgname = _tcsdup(comports[0].dev);
    comports[cnt].name = _tcsdup(L"NET (host)");
    cnt++;
    comports[cnt].dev = _tcsdup(L"ENET:L");
    comports[cnt].cfgname = _tcsdup(comports[1].dev);
    comports[cnt].name = _tcsdup(L"NET (client)");
    cnt++;
    #endif

    cnt = enumports_2(comports, cnt, false);
    j = 0;
    for (i = 0; i < 10; i++)
    {
        _stprintf(name, L"COM%d", i);
        if (!QueryDosDevice(name, devname, sizeof devname / sizeof(TCHAR)))
            continue;
        for (j = 0; j < cnt; j++)
        {
            if (!_tcscmp(comports[j].cfgname, name))
                break;
        }
        if (j == cnt)
        {
            if (cnt >= MAX_SERPAR_PORTS)
                break;

            TCHAR str[100];
            _stprintf(str, L"\\.\\\\%s", name);
            comports[j].dev = str;

            comports[j].cfgname = name;
            comports[j].name = name;

            if (Logging::PARSER_DEBUG)
                Logger::Write(L"SERPORT: %d:'%s' = '%s' (%s)\n", cnt, comports[j].name, comports[j].dev, devname);

            cnt++;
            j++;
        }
    }

    if (cnt < MAX_SERPAR_PORTS)
    {
        comports[cnt].dev = L"TCP://0.0.0.0:1234";
        comports[cnt].cfgname = comports[cnt].dev;
        comports[cnt].name = comports[cnt].dev;
        cnt++;
    }

    if (Logging::PARSER_DEBUG)
        Logger::Write(L"Parallel port enumeration..\n");

    enumports_2(parports, 0, true);

    if (Logging::PARSER_DEBUG)
        Logger::Write(L"Port enumeration end\n");

    return cnt;
}

int enummidiports()
{
    MIDIOUTCAPS midiOutCaps;
    MIDIINCAPS midiInCaps;
    int i, num, total;

    Logger::Write(L"MIDI port enumeration..\n");
    num = midiOutGetNumDevs();
    for (i = 0; i < num + 1 && i < MAX_MIDI_PORTS; i++)
    {
        if (midiOutGetDevCaps(i - 1, &midiOutCaps, sizeof(midiOutCaps)) != MMSYSERR_NOERROR)
            break;
        midioutportinfo[i].name = _tcsdup(midiOutCaps.szPname);
        Logger::Write(L"MIDI OUT: '%s' (%d/%d)\n", midioutportinfo[i].name, midiOutCaps.wMid, midiOutCaps.wPid);
    }
    total = num;

    num = midiInGetNumDevs();
    for (i = 0; i < num && i < MAX_MIDI_PORTS; i++)
    {
        if (midiInGetDevCaps(i, &midiInCaps, sizeof(midiInCaps)) != MMSYSERR_NOERROR)
            break;
        midiinportinfo[i].name = _tcsdup(midiInCaps.szPname);
        Logger::Write(L"MIDI IN: '%s' (%d/%d)\n", midiinportinfo[i].name, midiInCaps.wMid, midiInCaps.wPid);
    }
    total += num;

    Logger::Write(L"MIDI port enumeration end\n");

    return total;
}

void sernametodev(CString* sername)
{
    int i;

    for (i = 0; i < MAX_SERPAR_PORTS && (comports[i].name != ""); i++)
    {
        if (!_tcscmp(*sername, comports[i].cfgname))
        {
            *sername = comports[i].dev;
            return;
        }
    }

    if (!_tcsncmp(*sername, L"TCP:", 4))
        return;

    *sername = "";
}

void serdevtoname(CString* sername)
{
    int i;
    if (!_tcsncmp(*sername, L"TCP:", 4))
        return;
    for (i = 0; i < MAX_SERPAR_PORTS && (comports[i].name != ""); i++)
    {
        if (!_tcscmp(*sername, comports[i].dev))
        {
            *sername = comports[i].cfgname;
            return;
        }
    }
    *sername = "";
}